package la.iok.hzsvn.lewin.movie.obs;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.obs.services.ObsClient;
import com.obs.services.model.*;
import com.obs.services.model.fs.GetAttributeRequest;
import com.obs.services.model.fs.ObsFSAttribute;
import la.iok.hzsvn.lewin.movie.config.HuaweiServerProperties;
import la.iok.hzsvn.lewin.movie.config.RedisKey;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.model.ObsSignedUrl;
import la.iok.hzsvn.lewin.movie.model.SignUrlParam;
import la.iok.hzsvn.redis.service.RedisService;
import la.iok.hzsvn.share.annotations.Nullable;
import la.iok.hzsvn.share.dfs.exception.DfsException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class HuaweiObsServiceImpl implements ObsService{
    private static final int bufferSize = 1024*10;
    private static final String[] methods = new String[]{"password"};
    private static final String[] TOKEN_METHODS = new String[]{"token"};
    private static final Logger logger = LoggerFactory.getLogger(ObsService.class);
    private final HuaweiServerProperties huaweiServerProperties;
    private final RestTemplate restTemplate;
    /**
     * 读写权限的token
     */
    private HuaweiTokenRes huaweiWriterToken;
    /**
     * 只有读权限的token
     */
    private HuaweiTokenRes huaweiReaderToken;
    private final RedisService redisService;
    private final RedisKey redisKey;

    public HuaweiObsServiceImpl(HuaweiServerProperties huaweiServerProperties,
                                RestTemplate restTemplate,
                                RedisService redisService,
                                RedisKey redisKey) {
        this.huaweiServerProperties = huaweiServerProperties;
        this.restTemplate = restTemplate;
        this.redisService = redisService;
        this.redisKey = redisKey;
    }

    /**
     * 登录获取令牌
     * @param enableWrite 是否需要启用写权限。如果为true,则使用有写权限的账号获取,否则只使用读权限的账号
     */
    private boolean login(boolean enableWrite){
        try{
            HttpEntity<LoginRequest> entity = new HttpEntity<>(new LoginRequest(huaweiServerProperties.getDomain(),
                    huaweiServerProperties.name(enableWrite), huaweiServerProperties.password(enableWrite)));
            ResponseEntity<HuaweiTokenRes> loginResponse = restTemplate.exchange("https://iam.myhuaweicloud.com/v3/auth/tokens",
                    HttpMethod.POST,entity, HuaweiTokenRes.class);
            if(loginResponse.getStatusCode().is2xxSuccessful()){
                HuaweiTokenRes res = loginResponse.getBody();
                List<String> tokens = loginResponse.getHeaders().get("X-Subject-Token");
                if(!CollectionUtils.isEmpty(tokens)){
                    if(res != null){
                        res.setTokenStr(tokens.get(0));
                        if(enableWrite){
                            huaweiWriterToken = res;
                        }else{
                            huaweiReaderToken = res;
                        }
                        return true;
                    }else{
                        logger.error("获取token失败:{}",loginResponse);
                    }
                }else{
                    logger.error("token为空:{}",loginResponse);
                }
            }else {
                logger.error("请求获取token失败:{}",loginResponse);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
    private String getToken(boolean enableWrite){
        HuaweiTokenRes huaweiTokenRes = theToken(enableWrite);
        if(huaweiTokenRes == null){
            preAuth(enableWrite);
        }
        huaweiTokenRes = theToken(enableWrite);
        if(huaweiTokenRes == null){
            return null;
        }
        if(huaweiTokenRes.getToken() == null || huaweiTokenRes.getToken().nearExpire() || huaweiTokenRes.getToken().expired()){
            preAuth(enableWrite);
        }
        huaweiTokenRes = theToken(enableWrite);
        return huaweiTokenRes != null?huaweiTokenRes.getTokenStr():null;
    }

    private HuaweiTokenRes theToken(boolean enableWrite){
        return enableWrite ? huaweiWriterToken : huaweiReaderToken;
    }

    private SecurityTokenRes getAkSk(boolean enableWrite){
        try{
            return doGetAkSk(enableWrite);
        }catch (HttpClientErrorException.Unauthorized e){
            //可能令牌已经失效了，但是未过期
            if(login(enableWrite)){
                try{
                    return doGetAkSk(enableWrite);
                }catch (Exception ex){
                    logger.error("重新登录后再次获取AK/SK异常",e);
                }
            }
        }catch (Exception e){
            logger.error("获取AK/SK异常",e);
        }
        return null;
    }

    private SecurityTokenRes doGetAkSk(boolean enableWrite){
        String url = "https://iam.myhuaweicloud.com/v3.0/OS-CREDENTIAL/securitytokens";
        String token = getToken(enableWrite);
        if(!StringUtils.hasText(token)){
            logger.error("未获取到token");
            return null;
        }
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("X-Auth-Token",token);
        HttpEntity<LoginRequest> entity = new HttpEntity<>(new LoginRequest(3600),headers);
        ResponseEntity<SecurityTokenRes> loginResponse = restTemplate.exchange(url,
                HttpMethod.POST,entity, SecurityTokenRes.class);
        if(loginResponse.getStatusCode().is2xxSuccessful()){
            return loginResponse.getBody();
        }else {
            logger.error("请求获取AK/SK失败:{}",loginResponse);
            return null;
        }
    }

    private synchronized void preAuth(boolean enableWrite) {
        HuaweiTokenRes huaweiTokenRes = theToken(enableWrite);
        if(huaweiTokenRes == null || huaweiTokenRes.getToken() == null
                || huaweiTokenRes.getToken().nearExpire() || huaweiTokenRes.getToken().expired()){
            login(enableWrite);
        }
    }

    @Override
    public AccessKey getAccessKey(boolean enableWrite) {
        SecurityTokenRes res = getAkSk(enableWrite);
        if(res != null){
            Credential credential = res.getCredential();
            if(credential.nearExpire()){
                logger.warn("获取的访问密钥快过期了");
            }
            if(credential.expired()){
                logger.error("获取的访问密钥已经过期了");
            }
            AccessKey accessKey = new AccessKey();
            BeanUtils.copyProperties(credential,accessKey);
            accessKey.setSecurityToken(credential.getSecuritytoken());
            accessKey.setExpiresAt(credential.expireAt());
            accessKey.setRegion(huaweiServerProperties.getRegion());
            accessKey.setBucket(huaweiServerProperties.getBucketName());
            accessKey.setServerType("huawei");
            return accessKey;
        }
        return null;
    }

    @Override
    public ObsSignedUrl signUrlPut(@Nullable String key) {
        if(StringUtils.hasText(key)){
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey,key,HttpMethodEnum.PUT);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPut(String key, Map<String, Object> params) {
        if(StringUtils.hasText(key)){
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey,key,HttpMethodEnum.PUT,null,null,params);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlGet(@Nullable String key) {
        if(StringUtils.hasText(key)){
            AccessKey accessKey = getAccessKey(false);
            return signUrl(accessKey, key, HttpMethodEnum.GET);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPost(@Nullable String key, @Nullable SpecialParamEnum specialParamEnum) {
        if(StringUtils.hasText(key)){
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey, key, HttpMethodEnum.POST,null,specialParamEnum,null);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public ObsSignedUrl signUrlPost(String key, Map<String, String> headers, Map<String, Object> params) {
        if(StringUtils.hasText(key)){
            AccessKey accessKey = getAccessKey(true);
            return signUrl(accessKey, key, HttpMethodEnum.POST,headers,null,params);
        }
        return ObsSignedUrl.notExist();
    }

    @Override
    public void uploadFile(String storePath, File file) {
        long len = file.length();
        if(len <= 100*1024*1024){
            //100M以内单个文件上传
            uploadNormalFile(storePath,file);
        }else if(len < 5L *1024*1024*10000){
            //5万M以内,单个分段5M
            uploadLargeFile(storePath,file,5*1024*1024);
        }else{
            //最大分段10000个，计算每个的大小,但是单个分段大小必须在5G以内.按实际情况,每个分段最大1G以内就可以了
            long s = len / 10000;
            if(s >= 1024*1024*1024){
                throw new DfsException("文件大小[" + len + "]超过最大值");
            }
            uploadLargeFile(storePath,file,s);
        }
    }

    @Override
    public void downloadFile(String key, OutputStream os) {
        AccessKey accessKey = getAccessKey(false);
        if(accessKey == null || accessKey.expired()){
            throw new DfsException("获取下载密钥失败");
        }
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            try(InputStream is = obsClient.getObject(huaweiServerProperties.getBucketName(), key).getObjectContent()){
                byte[] buffer = new byte[bufferSize];
                int len;
                while ((len = is.read(buffer)) > 0){
                    os.write(buffer,0,len);
                }
                os.flush();
            }
        }catch (IOException e){
            throw new DfsException("下载文件失败",e);
        }
    }

    @Override
    public void deleteFile(String key) {
        AccessKey accessKey = getAccessKey(true);
        if(accessKey == null || accessKey.expired()){
            throw new DfsException("获取密钥失败");
        }
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            if(obsClient.doesObjectExist(huaweiServerProperties.getBucketName(), key)){
                obsClient.deleteObject(huaweiServerProperties.getBucketName(), key);
            }
        }catch (IOException e){
            throw new DfsException("删除文件失败",e);
        }
    }

    @Override
    public boolean exist(String key) {
        AccessKey accessKey = getAccessKey(true);
        if(accessKey == null || accessKey.expired()){
            throw new DfsException("获取访问密钥失败");
        }
        return exist(key,accessKey);
    }

    @Override
    public long fileLength(String key) {
        AccessKey accessKey = getAccessKey(false);
        if(accessKey == null || accessKey.expired()){
            throw new DfsException("获取访问密钥失败");
        }
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            ObsFSAttribute attr = obsClient.getAttribute(new GetAttributeRequest(huaweiServerProperties.getBucketName(),key));
            if(attr != null){
                return attr.getContentLength();
            }
        }catch (IOException e){
            throw new DfsException("获取文件大小失败",e);
        }
        return 0;
    }

    @Override
    public ObsSignedUrl signUrl(SignUrlParam param) {
        HttpMethodEnum method = HttpMethodEnum.getValueFromStringCode(param.getMethod());
        AccessKey accessKey;
        String key;
        if(method == HttpMethodEnum.POST || method == HttpMethodEnum.PUT || method == HttpMethodEnum.DELETE){
            accessKey = getAccessKey(true);
            key = (String) redisService.get(redisKey.signedUrlStorePathKey(param.getUrlId()));
            ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(key,"urlId","查询的记录不存在");
        }else{
            accessKey = getAccessKey(false);
            key = param.getKey();
            ErrorCode.PROPERTY_CHECK_ERROR.assertNotBlank(key,"key","查询的对象key不能为空");
        }
        SpecialParamEnum specialParam = null;
        if(StringUtils.hasText(param.getSpecialParam())){
            specialParam = SpecialParamEnum.getValueFromStringCode(param.getSpecialParam());
        }
        return signUrl(accessKey,key,method,param.getHeaders(),specialParam,param.getParams());
    }

    @Override
    public List<Multipart> loadUploadPart(String key, String uploadId) {
        AccessKey accessKey = getAccessKey(false);
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            ListPartsRequest request = new ListPartsRequest(huaweiServerProperties.getBucketName(), key, uploadId);
            ListPartsResult result = obsClient.listParts(request);
            return result.getMultipartList();
        }catch (IOException e){
            throw new DfsException("检查文件是否存在",e);
        }
    }

    private boolean exist(String key,AccessKey accessKey){
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            return obsClient.doesObjectExist(huaweiServerProperties.getBucketName(), key);
        }catch (IOException e){
            throw new DfsException("检查文件是否存在失败",e);
        }
    }

    /**
     * 获取写操作的访问令牌
     * @return 访问令牌
     */
    @NotNull
    private AccessKey writeAccessKey() {
        AccessKey accessKey = getAccessKey(true);
        if(accessKey == null || accessKey.expired()){
            throw new DfsException("获取上传密钥失败");
        }
        return accessKey;
    }

    private void uploadNormalFile(String storePath, File file){
        AccessKey accessKey = writeAccessKey();
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            obsClient.putObject(huaweiServerProperties.getBucketName(),storePath,file);
        }catch (IOException e){
            throw new DfsException("上传文件失败",e);
        }
    }
    private void uploadLargeFile(String storePath, File file, long segmentSize){
        AccessKey accessKey = writeAccessKey();
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(huaweiServerProperties.getBucketName(),storePath);
            InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
            if(!StringUtils.hasText(result.getUploadId())){
                throw new DfsException("初始化分段上传失败");
            }
            long len = file.length();
            long parts = len / segmentSize;
            if(len % segmentSize != 0){
                parts++;
            }
            List<PartEtag> partEtags = new ArrayList<>();
            for(int part = 1; part <= parts; ++part){
                UploadPartRequest req = new UploadPartRequest(huaweiServerProperties.getBucketName(),storePath);
                req.setUploadId(result.getUploadId());
                req.setPartNumber(part);
                req.setFile(file);
                req.setPartSize(segmentSize);
                req.setOffset((part-1)*segmentSize);
                req.setAttachMd5(true);
                UploadPartResult res = obsClient.uploadPart(req);
                partEtags.add(new PartEtag(res.getEtag(),res.getPartNumber()));
            }
            if(partEtags.size() == parts){
                //成功了
                CompleteMultipartUploadRequest completeReq = new CompleteMultipartUploadRequest(
                        huaweiServerProperties.getBucketName(),storePath,result.getUploadId(),partEtags);
                obsClient.completeMultipartUpload(completeReq);
            }else{
                logger.error("分段上传文件结果异常：应该有{}段,实际上只有{}段",parts,partEtags.size());
                AbortMultipartUploadRequest abortReq = new AbortMultipartUploadRequest(
                        huaweiServerProperties.getBucketName(),storePath,result.getUploadId());
                obsClient.abortMultipartUpload(abortReq);
                throw new DfsException("分段上传文件"+file.getName()+"失败");
            }
        }catch (IOException e){
            throw new DfsException("分段上传文件"+file.getName()+"失败",e);
        }
    }

    @NotNull
    private ObsSignedUrl signUrl(AccessKey accessKey, @Nullable String key, HttpMethodEnum method) {
        return signUrl(accessKey,key,method,null,null,null);
    }

    @NotNull
    private ObsSignedUrl signUrl(AccessKey accessKey, @Nullable String key, HttpMethodEnum method, Map<String,String> headers, SpecialParamEnum specialParamEnum, Map<String, Object> params){
        try(ObsClient obsClient = new ObsClient(accessKey.getAccess(),accessKey.getSecret(),accessKey.getSecurityToken(),
                huaweiServerProperties.getEndPoint())){
            TemporarySignatureRequest req = new TemporarySignatureRequest(method,huaweiServerProperties.getBucketName(),key,specialParamEnum,3600);
            if(params != null){
                req.setQueryParams(params);
            }
            if(headers != null){
                req.setHeaders(headers);
            }
            TemporarySignatureResponse res = obsClient.createTemporarySignature(req);
            ObsSignedUrl url = new ObsSignedUrl();
            url.setId(UUID.randomUUID().toString());
            url.setUrl(res.getSignedUrl());
            url.setHeaders(res.getActualSignedRequestHeaders());
            if(method == HttpMethodEnum.POST || method == HttpMethodEnum.PUT || method == HttpMethodEnum.DELETE){ //修改操作才写入redis记录
                redisService.set(redisKey.signedUrlStorePathKey(url.getId()),key,1, TimeUnit.HOURS);  //1小时过期时间
            }
            return url;
        } catch (IOException e) {
            throw new DfsException("签名url失败",e);
        }
    }

    private static class Domain{
        private String name;
        public Domain(){

        }
        public Domain(String name){
            this.name = name;
        }
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
    private static class User{
        private Domain domain;
        private String name;
        private String password;
        public User(){

        }
        public User(String domain,String name,String password){
            this.domain = new Domain(domain);
            this.name = name;
            this.password = password;
        }
        public Domain getDomain() {
            return domain;
        }

        public void setDomain(Domain domain) {
            this.domain = domain;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }
    }
    private static class Password{
        private User user;

        public Password() {
        }

        public Password(User user) {
            this.user = user;
        }

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }
    }
    private static class Identity{
        private String[] methods;

        public Identity() {
        }

        public Identity(String[] methods){
            this.methods = methods;
        }

        public String[] getMethods() {
            return methods;
        }

        public void setMethods(String[] methods) {
            this.methods = methods;
        }
    }
    private static class PasswordIdentity extends Identity{
        private Password password;
        public PasswordIdentity(){
        }
        public PasswordIdentity(Password password) {
            super(HuaweiObsServiceImpl.methods);
            this.password = password;
        }
        public Password getPassword() {
            return password;
        }

        public void setPassword(Password password) {
            this.password = password;
        }
    }
    private static class TokenReq {
        @JsonProperty("duration_seconds")
        private int durationSeconds;

        public TokenReq() {
        }

        public TokenReq(int durationSeconds) {
            this.durationSeconds = durationSeconds;
        }

        public int getDurationSeconds() {
            return durationSeconds;
        }

        public void setDurationSeconds(int durationSeconds) {
            this.durationSeconds = durationSeconds;
        }
    }
    private static class TokenIdentity extends Identity {
        private TokenReq token;

        public TokenIdentity() {
        }

        public TokenIdentity(TokenReq token) {
            super(TOKEN_METHODS);
            this.token = token;
        }

        public TokenReq getToken() {
            return token;
        }

        public void setToken(TokenReq token) {
            this.token = token;
        }
    }
    private static class Auth{
        private Identity identity;

        public Auth() {
        }

        public Auth(Identity identity) {
            this.identity = identity;
        }

        public Identity getIdentity() {
            return identity;
        }

        public void setIdentity(Identity identity) {
            this.identity = identity;
        }
    }
    private static class LoginRequest{
        private Auth auth;

        public LoginRequest() {
        }

        public LoginRequest(String domain,String name,String password) {
            this.auth = new Auth(new PasswordIdentity(new Password(new User(domain,name,password))));
        }

        public LoginRequest(int durationSeconds){
            this.auth = new Auth(new TokenIdentity(new TokenReq(durationSeconds)));
        }

        public Auth getAuth() {
            return auth;
        }

        public void setAuth(Auth auth) {
            this.auth = auth;
        }
    }

    /**
     * 角色
     */
    private static class Role {
        private String id;
        private String name;

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    private static class HuaweiToken implements Expirable {
        @JsonFormat(locale="zh", timezone="GMT", pattern="yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")
        @JsonProperty("expires_at")
        private LocalDateTime expiresAt;
        private Domain domain;
        private List<Role> roles;
        @JsonFormat(locale="zh", timezone="GMT", pattern="yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")
        @JsonProperty("issued_at")
        private LocalDateTime issuedAt;
        private User user;

        public LocalDateTime getExpiresAt() {
            return expiresAt;
        }

        public void setExpiresAt(LocalDateTime expiresAt) {
            this.expiresAt = expiresAt;
        }

        public Domain getDomain() {
            return domain;
        }

        public void setDomain(Domain domain) {
            this.domain = domain;
        }

        public List<Role> getRoles() {
            return roles;
        }

        public void setRoles(List<Role> roles) {
            this.roles = roles;
        }

        public LocalDateTime getIssuedAt() {
            return issuedAt;
        }

        public void setIssuedAt(LocalDateTime issuedAt) {
            this.issuedAt = issuedAt;
        }

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }

        @Override
        public Date expireAt() {
            return Date.from(expiresAt.toInstant(ZoneOffset.UTC));
        }
    }
    private static class HuaweiTokenRes {
        private HuaweiToken token;
        private String tokenStr;

        public HuaweiToken getToken() {
            return token;
        }

        public void setToken(HuaweiToken token) {
            this.token = token;
        }

        public String getTokenStr() {
            return tokenStr;
        }

        public void setTokenStr(String tokenStr) {
            this.tokenStr = tokenStr;
        }
    }
    private static class Credential implements Expirable{
        private String access;
        @JsonFormat(locale="zh", timezone="GMT", pattern="yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")
        @JsonProperty("expires_at")
        private LocalDateTime expiresAt;
        private String secret;
        private String securitytoken;

        @Override
        public Date expireAt() {
            return Date.from(expiresAt.toInstant(ZoneOffset.UTC));
        }

        public String getAccess() {
            return access;
        }

        public void setAccess(String access) {
            this.access = access;
        }

        public LocalDateTime getExpiresAt() {
            return expiresAt;
        }

        public void setExpiresAt(LocalDateTime expiresAt) {
            this.expiresAt = expiresAt;
        }

        public String getSecret() {
            return secret;
        }

        public void setSecret(String secret) {
            this.secret = secret;
        }

        public String getSecuritytoken() {
            return securitytoken;
        }

        public void setSecuritytoken(String securitytoken) {
            this.securitytoken = securitytoken;
        }
    }
    private static class SecurityTokenRes {
        private Credential credential;

        public Credential getCredential() {
            return credential;
        }

        public void setCredential(Credential credential) {
            this.credential = credential;
        }
    }
}
